/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

/*This file is an appendage to fs.c, separating resource management from it*/
/*Move update_Read etc to process context*/

static struct fs *sched_waiting, *sched_active, *sched;

/**
Get the read count of a system node
*/
int fsres_get_read_count(u32 *read_count, struct fs *x)
{
	if(x->system) {
		*read_count = x->system->read_count;
		return 0;
	}
	return ESTATEINV;
}

/**
Get the write count of the system node
*/
int fsres_get_write_count(u32 *write_count, struct fs *x)
{
	if(x->system) {
		*write_count = x->system->write_count;
		return 0;
	}
	return ESTATEINV;
}


/**
 This function is called when an entity reads more than what is available. This makes it sleep for it
 */
int fsres_wait_on_read(struct fs *on, struct fs *owner)
{
	struct fs *this_task;
	struct listener n;
	int err;
	n.flags = RES_TASK | RES_READ; //We are a task who is waiting to read
	n.owner = owner;
	this_task = current->fs;
	n.task = this_task;
	//arch_block_interrupts();
	fsres_attach_listener(on, &n);
	fs_move(sched_waiting, this_task);
	fs_write(owner, sched, NULL, 0, 0); //Reschedule to take control from us
	//arch_allow_interrupts();
	return 0;
}

/**
 This function is called when an entity writes more than what is writeable. This makes it sleep for it
 */
int fsres_wait_on_write(struct fs *on, struct fs *owner)
{
	struct fs *this_task;
	struct listener n;
	int err;
	n.flags = RES_TASK | RES_WRITE; //We are a task who is waiting to read
	n.owner = owner;
	this_task = current->fs;
	n.task = this_task;
	fsres_attach_listener(on, &n);
	err = fs_move(sched_waiting, this_task);
	if(err)
		return err;
	return fs_write(owner, sched, NULL, 0, 0); //Reschedule to take control from us
}




/**
Update the resource so that more read is possible, in turn, waking up or calling the listeners
*/
int fsres_update_resource_read(struct fs *x, u32 read_count)
{
	x->system->read_count = read_count;
	fsres_rw_listeners(x, RW_R);
}

/**
Update the resource so that more write is possible, in turn, waking up or calling the listeners
*/
int fsres_update_resource_write(struct fs *x, u32 write_count)
{
	x->system->write_count = write_count;
	fsres_rw_listeners(x, RW_W);
}



/**
Functions to add enumerator and listener to the list
*/
int fsres_attach_enumerator(struct fs *x, struct enumerator *enumerator)
{
	LIST_ATTACH(x->system->enumerators, x->system->enumeratorstail, enumerator);
	return 0;
}

/**
Functions to add enumerator and listener to the list
*/
int fsres_attach_listener(struct fs *x, struct listener *listener)
{
	LIST_ATTACH(x->system->listeners, x->system->listenerstail, listener);
	return 0;
}


/**
Add a function to be called when the children of an fs node are about to be enumerated
*/
int fsres_add_enumerator(struct fs *x, struct fs *owner, int (*enumerate)(struct fs *this))
{
	struct enumerator *enumerator;
	if(!(x->system)) //Not a kernel system
		return ENOREG;
	enumerator = (struct enumerator *)malloc_get(sizeof(struct enumerator), "fsenum");
	if(!enumerator)
		return EMEMERR;
	enumerator->next = enumerator->prev = NULL;
	enumerator->enumerate = enumerate;
	enumerator->owner = owner;
	enumerator->flags = RES_CHILD;
	return fsres_attach_enumerator(x, enumerator);
}

/**
Add a function that will be called when children appear in an fs node
*/
int fsres_add_listener(struct fs *x, struct fs *owner, int (*notify)(struct fs *this, struct fs *child))
{
	struct listener *listener;
	if(!(x->system)) //Not a kernel system
		return ENOREG;
	listener = (struct listener *)malloc_get(sizeof(struct listener), "fslis");
	if(!listener)
		return EMEMERR;
	listener->next = listener->prev = NULL;
	listener->notify = notify;
	listener->owner = owner;
	listener->flags = RES_CHILD;

	return fsres_attach_listener(x, listener);
}



/**
---------------------------------------------Calls/wake ups----------------------------------
*/


/**
Local function that will wake a task by moving it's task struct from /sched/waiting to /sched/active
*/
static void fsres_wake_task(struct fs *task_fs)
{
	fs_move(sched_active, task_fs);
	arch_schedule_schedule_on_irq();
}


/**
This function calls the entity that has registered to be called if the node is being enumerated
*/
int fsres_child_enumerators(struct fs *x)
{
	struct enumerator *e;
	LIST_FOR(x->system->enumerators, e) {
		if(e->flags & RES_CHILD) {
			if(e->flags & RES_TASK) {
				fsres_wake_task(e->task);
			} else {
				e->enumerate(x);
			}
		}
	}
	return 0;
}

/**
This function calls the entity that has registered to be called if the node gets a new child
*/
int fsres_child_listeners(struct fs *x, struct fs *child)
{
	struct listener *l;
	if(x->system) {
		LIST_FOR(x->system->listeners, l) {
			if(l->flags & RES_TASK) {
				fsres_wake_task(l->task);
			} else {
				l->notify(x, child);
			}
		}
	} else
		return ESTATEINV;
	return 0;
}


/**
Call/wake enumerators waiting to provide data
*/
int fsres_rw_enumerators(struct fs *x, int rw)
{
	struct enumerator *e, *tr;
	e = x->system->enumerators;
	while(e) {
		if(e->flags & RES_READ) {
			if(e->flags & RES_TASK) {
				fsres_wake_task(e->task);
			} else {
				if(rw == RW_R)
					e->enumerate_read(x);
				else if(rw==RW_W)
					e->enumerate_write(x);
			}
		}
		tr = e;
		e = e->next;
		LIST_DETACH(x->system->enumerators, x->system->enumeratorstail, e);
	}
	return 0;
}

/**
Call/wake enumerators waiting for data
*/
int fsres_rw_listeners(struct fs *x, int rw)
{
	struct listener *l, *tr;
	l = x->system->listeners;
	while(l) {
		if(l->flags & RES_READ) {
			if(l->flags & RES_TASK) {
				fsres_wake_task(l->task);
			} else {
				if(rw == RW_R)
					l->notify_read(x);
				else if(rw==RW_W)
					l->notify_write(x);
			}
		}
		tr = l;
		l = l->next;
		LIST_DETACH(x->system->listeners, x->system->listenerstail, tr);
	}
	return 0;
}


/**
Get pointers to the sched directories. This requires the sched system to be initiated before
*/
int fsres_init()
{
	dprint(D_PROG, "fsres", "Initialize");
	sched = fsparse_get("/sched");
	sched_waiting = fsparse_get("/sched/waiting");
	sched_active = fsparse_get("/sched/active");
	return 0;
}
